package cn.tedu.knows.auth.security;

import cn.tedu.knows.auth.service.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.annotation.Resource;
import java.util.Arrays;

@Configuration
// 这个注解表示当前类是配置授权服务器类
// 和Oauth2框架的组件结合读取下面配置,实现授权功能
@EnableAuthorizationServer
public class AuthorizationServer extends
                AuthorizationServerConfigurerAdapter {
    // 添加依赖:授权管理器
    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    private UserDetailsServiceImpl userDetailsService;

    //  配置授权服务器各种登录参数
    @Override
    public void configure(
            AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // Oauth2框架实际上提供的标准在java程序中体现为
        // 提供了很多控制器方法,这里的endpoints,翻译为端点
        // 就是浏览器访问授权服务器时,Oauth2提供的控制器方法
        endpoints
                // 设置可用的授权管理器对象
                .authenticationManager(authenticationManager)
                // 设置登录时运行的获得用户详情的方法
                .userDetailsService(userDetailsService)
                // 设置登录时允许的方法方法(只允许post方式访问)
                .allowedTokenEndpointRequestMethods(HttpMethod.POST)
                // 配置生成令牌的对象
                .tokenServices(tokenService());
    }
    // 添加依赖:令牌生成规则,我们自己保存到Spring容器中的
    @Resource
    private TokenStore tokenStore;
    // 添加依赖:客户端详情类,
    //  是SpringCloudSecurity结合Oauth2提供的
    @Resource
    private ClientDetailsService clientDetailsService;

    @Resource
    private JwtAccessTokenConverter accessTokenConverter;

    // 配置生成令牌的方法,需要保存到Spring容器中
    @Bean
    public AuthorizationServerTokenServices tokenService(){
        // 创建TokenServices实现类对象
        DefaultTokenServices services=
                                new DefaultTokenServices();
        // 设置令牌生成规则
        services.setTokenStore(tokenStore);
        // ↓↓↓↓↓↓↓新增代码↓↓↓↓↓↓↓↓↓↓↓↓
        // 实例化令牌增强对象,使用Jwt令牌
        TokenEnhancerChain chain=new TokenEnhancerChain();
        // 将jwt转换器设置到令牌增强对象中
        chain.setTokenEnhancers(
                    Arrays.asList(accessTokenConverter));
        // 设置令牌增强对象到令牌生成器中
        services.setTokenEnhancer(chain);
        // ↑↑↑↑↑↑↑新增代码结束↑↑↑↑↑↑↑↑↑↑↑↑↑

        // 设置令牌有效期(单位是秒 3600 是一小时)
        services.setAccessTokenValiditySeconds(3600);
        // 设置客户端详情
        services.setClientDetailsService(clientDetailsService);
        // 别忘了返回
        return services;
    }

    // 获得加密对象
    @Resource
    private PasswordEncoder passwordEncoder;
    // 设置当前授权服务器许可的客户端信息

    @Override
    public void configure(ClientDetailsServiceConfigurer
                              clients) throws Exception {
       clients.inMemory()  //内存保存客户端信息
               // 设置客户端名称(客户端id)
               .withClient("knows")
               // 客户端登录要密码,密码要加密
               .secret(passwordEncoder.encode("123456"))
               // 赋予客户端权限(一个名字)
               .scopes("main")
               // 设置允许客户端登录的类型
               // "password"不能写错是框架识别的关键字符串
               .authorizedGrantTypes("password");

    }
    // 如果认证成功,当前客户端可以使用的Oauth2的资源设置
    @Override
    public void configure(
        AuthorizationServerSecurityConfigurer security)
                                       throws Exception {
        security
            //是否允许当前客户端访问生成令牌的端点
            .tokenKeyAccess("permitAll()")
            //是否允许当前客户端访问验证令牌的端点
            .checkTokenAccess("permitAll()")
            // 允许通过验证的客户端获得令牌
            .allowFormAuthenticationForClients();
    }
}
